/*opyright (C) 2007 Red Hat, Inc. All Rights Reserved.
 * Written by David Howells (dhowells@redhat.com)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version
 * 2 of the License, or (at your option) any later version.
 *
 * Based on code:
 *
 * Copyright (c) 1995 - 2000 Kungliga Tekniska H?gskolan
 * (Royal Institute of Technology, Stockholm, Sweden).
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the Institute nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#define _XOPEN_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <ctype.h>
#include <time.h>
#include <signal.h>
#include <errno.h>
#include <termios.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <keyutils.h>
//#include <rpc/xdr.h>
#include <kerberosIV/des.h>
#include <getopt.h>
#include <sys/types.h>
#include <netdb.h>


#define FLAG_P 0x00000001
#define FLAG_M 0x00000002
#define FLAG_A 0x00000004
 #define BUFFERSIZE 1024

#define KA_SERVICE 731
#define KA_TICKET_GRANTING_SERVICE 732

#define OSERROR(X, Y) do { if ((long)(X) == -1) { perror(Y); exit(1); } } while(0)

struct sockaddr_rxrpc {
       sa_family_t     srx_family;     /* address family */
       unsigned short  srx_service;    /* service desired */
       unsigned short  transport_type; /* type of transport socket (SOCK_DGRAM) */
       unsigned short  transport_len;  /* length of transport address */
       union {
               sa_family_t family;             /* transport address family */
               struct sockaddr_in sin;         /* IPv4 transport address */
               struct sockaddr_in6 sin6;       /* IPv6 transport address */
       } transport;
};

#define AF_RXRPC                33
#define PF_RXRPC                AF_RXRPC
#define SOL_RXRPC               272
#define RXRPC_USER_CALL_ID      1       /* User call ID specifier */
#define RXRPC_ABORT             2       /* Abort request / notification */
#define RXRPC_ACK               3       /* [Server] RPC op final ACK received */
#define RXRPC_RESPONSE          4       /* [Server] security response received */
#define RXRPC_NET_ERROR         5       /* network error received */
#define RXRPC_BUSY              6       /* server busy received */
#define RXRPC_LOCAL_ERROR       7       /* local error generated */
#define RXRPC_PREPARE_CALL_SLOT 8       /* Propose user call ID specifier for next call */
#define RXRPC_SECURITY_KEY              1       /* [clnt] set client security key */
#define RXRPC_SECURITY_KEYRING          2       /* [srvr] set ring of server security keys */
#define RXRPC_EXCLUSIVE_CONNECTION      3       /* [clnt] use exclusive RxRPC connection */
#define RXRPC_MIN_SECURITY_LEVEL        4       /* minimum security level */

#define OSERROR(X, Y) do { if ((long)(X) == -1) { perror(Y); exit(1); } } while(0)

static const unsigned char local_addr[4] = { 0, 0, 0, 0 };
//static const unsigned char remote_addr[4] = { 90, 155, 74, 22 };
 unsigned char remote_addr[4] = { 192,168,76,132};

#define RXRPC_ADD_CALLID(control, ctrllen, id)                          \
do {                                                                    \
       struct cmsghdr *__cmsg;                                         \
       __cmsg = (void *)(control) + (ctrllen);                         \
       __cmsg->cmsg_len        = CMSG_LEN(sizeof(unsigned long));      \
       __cmsg->cmsg_level      = SOL_RXRPC;                            \
       __cmsg->cmsg_type       = RXRPC_USER_CALL_ID;                   \
       *(unsigned long *)CMSG_DATA(__cmsg) = (id);                     \
       (ctrllen) += __cmsg->cmsg_len;                                  \
                                                                       \
} while (0)

#define RXRPC_ADD_ABORT(control, ctrllen, abort_code)                   \
do {                                                                    \
       struct cmsghdr *__cmsg;                                         \
       __cmsg = (void *)(control) + (ctrllen);                         \
       __cmsg->cmsg_len        = CMSG_LEN(sizeof(unsigned long));      \
       __cmsg->cmsg_level      = SOL_RXRPC;                            \
       __cmsg->cmsg_type       = RXRPC_ABORT;                          \
       *(unsigned long *)CMSG_DATA(__cmsg) = (abort_code);             \
       (ctrllen) += __cmsg->cmsg_len;                                  \
                                                                       \
} while (0)

const char KA_GETTGT_REQ_LABEL[] = "gTGS";
const char KA_GETTGT_RPL_LABEL[] = "tgsT";
const char KA_GETTKT_RPL_LABEL[] = "gtkt";
#define KAA_Authenticate        21
#define KAA_AuthenticateV2      22
#define KAT_GetToken            23

struct ka_ticket {
       des_cblock      session_key;
       time_t          end_time;
       int             kvno;
       int             ticket_len;
       const char      *name;
       const char      *instance;
       const char      *cell;
       const char      *server_name;
       const char      *server_instance;
       const void      *ticket;
       char            _reply[0];
};

struct rxrpc_key_sec2_v1 {
       uint32_t        kver;                   /* key payload interface version */
       uint16_t        security_index;         /* RxRPC header security index */
       uint16_t        ticket_length;          /* length of ticket[] */
       uint32_t        expiry;                 /* time at which expires */
       uint32_t        kvno;                   /* key version number */
       uint8_t         session_key[8];         /* DES session key */
       uint8_t         ticket[0];              /* the encrypted ticket */
};

/*****************************************************************************/
/*
 * dump the control messages
 */
static void dump_cmsg(struct msghdr *msg)
{
       struct cmsghdr *cmsg;
       unsigned long user_id;
       unsigned char *p;
       int abort_code;
       int n;

       for (cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
               n = cmsg->cmsg_len - CMSG_ALIGN(sizeof(*cmsg));
               p = CMSG_DATA(cmsg);

               printf("CMSG: %zu: ", cmsg->cmsg_len);

               if (cmsg->cmsg_level == SOL_RXRPC) {
                       switch (cmsg->cmsg_type) {
                       case RXRPC_USER_CALL_ID:
                               printf("RXRPC_USER_CALL_ID: ");
                               if (n != sizeof(user_id))
                                       goto dump_data;
                               memcpy(&user_id, p, sizeof(user_id));
                               printf("%lx\n", user_id);
                               continue;

                       case RXRPC_ABORT:
                               printf("RXRPC_ABORT: ");
                               if (n != sizeof(abort_code))
                                       goto dump_data;
                               memcpy(&abort_code, p, sizeof(abort_code));
                               printf("%d\n", abort_code);
                               continue;

                       case RXRPC_ACK:
                               printf("RXRPC_ACK");
                               if (n != 0)
                                       goto dump_data_colon;
                               goto print_nl;

                       case RXRPC_RESPONSE:
                               printf("RXRPC_RESPONSE");
                               if (n != 0)
                                       goto dump_data_colon;
                               goto print_nl;

                       case RXRPC_NET_ERROR:
                               printf("RXRPC_NET_ERROR: ");
                               if (n != sizeof(abort_code))
                                       goto dump_data;
                               memcpy(&abort_code, p, sizeof(abort_code));
                               printf("%s\n", strerror(abort_code));
                               continue;

                       case RXRPC_BUSY:
                               printf("RXRPC_BUSY");
                               if (n != 0)
                                       goto dump_data_colon;
                               goto print_nl;

                       case RXRPC_LOCAL_ERROR:
                               printf("RXRPC_LOCAL_ERROR: ");
                               if (n != sizeof(abort_code))
                                       goto dump_data;
                               memcpy(&abort_code, p, sizeof(abort_code));
                               printf("%s\n", strerror(abort_code));
                               continue;

                       default:
                               break;
                       }
               }

               printf("l=%d t=%d", cmsg->cmsg_level, cmsg->cmsg_type);

       dump_data_colon:
               printf(": ");
       dump_data:
               printf("{");
               for (; n > 0; n--, p++)
                       printf("%02x", *p);

       print_nl:
               printf("}\n");
       }
}

void dump_data(void *p, size_t len)
{
#define INT_PER_LINE (8 * 4)
       size_t loop;
       unsigned char buf[41], *b, ch, yoffs;

       yoffs = 0;
       b = buf;
       for (loop = 0; loop < len; loop++) {
               if (!yoffs) {
                       printf("%08zx: ", loop);
                       yoffs = 1;
               }
               ch = *(const char *) p;
               printf("%02x", ch);
               *b++ = isprint(ch) ? ch : '.';
               p++;
               if (loop % INT_PER_LINE == INT_PER_LINE - 1) {
                       *b = 0;
                       printf(" %s\n", buf);
                       b = buf;
                       yoffs = 0;
               } else if (loop % 4 == 3) {
                       putchar(' ');
               }
       }

       if (loop % INT_PER_LINE > 0) {
               for (loop %= INT_PER_LINE; loop < INT_PER_LINE; loop++) {
                       printf("  ");
                       if (loop % 4 == 3)
                               putchar(' ');
               }
               *b = 0;
               printf("%s\n", buf);
       }
}

/*****************************************************************************/
/*
 *
 */
int ka_RPC(int service, const void *request, size_t reqlen, void *reply, size_t *_replen)
{
       struct sockaddr_rxrpc srx;
       struct msghdr msg;
       struct iovec iov[1];
       size_t ctrllen, replen;
       unsigned char control[4096];
       void *preply;
       int client, ret;

       client = socket(AF_RXRPC, SOCK_DGRAM, PF_INET);
       OSERROR(client, "socket");

       /* the authentication is associated with the virtual
        * connection, so we need to open an exclusive channel */
       ret = setsockopt(client, SOL_RXRPC, RXRPC_EXCLUSIVE_CONNECTION,
                        NULL, 0);
       OSERROR(ret, "setsockopt");

       /* bind an address to the local endpoint */
       srx.srx_family = AF_RXRPC;
       srx.srx_service = 0; /* it's a client */
       srx.transport_type = SOCK_DGRAM;
       srx.transport_len = sizeof(srx.transport.sin);
       srx.transport.sin.sin_family = AF_INET;
       srx.transport.sin.sin_port = htons(7001);
       memcpy(&srx.transport.sin.sin_addr, &local_addr, 4);

       ret = bind(client, (struct sockaddr *) &srx, sizeof(srx));
       OSERROR(ret, "bind");

       /* connect to the remote server */
       srx.srx_family = AF_RXRPC;
       srx.srx_service = service;
       srx.transport_type = SOCK_DGRAM;
       srx.transport_len = sizeof(srx.transport.sin);
       srx.transport.sin.sin_family = AF_INET;
       srx.transport.sin.sin_port = htons(7004);
       memcpy(&srx.transport.sin.sin_addr, &remote_addr, 4);

       ret = connect(client, (struct sockaddr *) &srx, sizeof(srx));
       OSERROR(ret, "connect");

       /* request an operation */
       ctrllen = 0;
       RXRPC_ADD_CALLID(control, ctrllen, 0x12345);

       iov[0].iov_base = (void *) request;
       iov[0].iov_len = reqlen;

       msg.msg_name            = NULL;
       msg.msg_namelen         = 0;
       msg.msg_iov             = iov;
       msg.msg_iovlen          = 1;
       msg.msg_control         = control;
       msg.msg_controllen      = ctrllen;
       msg.msg_flags           = 0;

       ret = sendmsg(client, &msg, 0);
       if (ret == -1 && (errno == ENOANO || errno == ECONNABORTED))
               perror("sendmsg/data");
       else
               OSERROR(ret, "sendmsg/data");

       /* wait for a reply */
       preply = reply;
       replen = 0;
       while (replen < *_replen) {
               iov[0].iov_base = preply;
               iov[0].iov_len = *_replen - replen;

               msg.msg_name    = NULL;
               msg.msg_namelen = 0;
               msg.msg_iov     = iov;
               msg.msg_iovlen  = 1;
               msg.msg_control = control;
               msg.msg_controllen = sizeof(control);
               msg.msg_flags   = 0;

               ret = recvmsg(client, &msg, 0);
               OSERROR(ret, "recvmsg");

               printf("RECV: %d [fl:%d]\n", ret, msg.msg_flags);
               printf("CMSG: %zu\n", msg.msg_controllen);
               printf("IOV: %zu [0]=%zu\n", msg.msg_iovlen, iov[0].iov_len);
               dump_cmsg(&msg);

               preply += ret;
               replen += ret;

               if (msg.msg_flags & MSG_EOR)
                       break;
       }

       *_replen = replen;
       close(client);
       return 0;
}

/*****************************************************************************/
/*
 * authenticate this RxRPC virtual connection with the KA server
 */
int ka_Authenticate(const char *principal,
                   const char *realm,
                   int service,
                   des_cblock _key,
                   time_t start_time,
                   time_t end_time,
                   struct ka_ticket **_tgt)
{
       struct ka_ticket *tgt;
       des_key_schedule sched;
       char buffer[16384] __attribute__((aligned(4)));
       char data[8], *p, *end, *q;
       unsigned int tmp;
       des_cblock key;
       size_t len;
       time_t challtime;
       int ret;

       memcpy(key, _key, sizeof(key));
       ret = des_key_sched(key, sched);
       OSERROR(ret, "des_key_sched");

       tmp = htonl(start_time);
       memcpy(data, &tmp, 4);
       memcpy(data + 4, KA_GETTGT_REQ_LABEL, 4);

       des_pcbc_encrypt((void *) data, (void *) data,
                        sizeof(data), sched, &key, DES_ENCRYPT);

       /* marshall the arguments */
       p = buffer;
       *(unsigned int *)p = htonl(KAA_AuthenticateV2);
       p += 4;

       len = strlen(principal);
       *(unsigned int *)p = htonl(len);
       p += 4;
       memcpy(p, principal, len);
       p += len;
       while ((unsigned long) p & 3)
               *p++ = 0;

       realm = NULL;
       if (realm) {
               len = strlen(realm);
               *(unsigned int *)p = htonl(len);
               p += 4;
               memcpy(p, realm, len);
               p += len;
               while ((unsigned long) p & 3)
                       *p++ = 0;
       } else {
               *(unsigned int *)p = htonl(0);
               p += 4;
       }

       *(unsigned int *)p = htonl(start_time);
       p += 4;
       *(unsigned int *)p = htonl(end_time);
       p += 4;

       len = sizeof(data);
       *(unsigned int *)p = htonl(len);
       p += 4;
       memcpy(p, data, len);
       p += len;
       while ((unsigned long) p & 3)
               *p++ = 0;

       *(unsigned int *)p = htonl(0x3044);
       p += 4;
       *(unsigned int *)p = htonl(0);
       p += 4;

       /* make the call */
       len = sizeof(buffer);
       ret = ka_RPC(KA_SERVICE, buffer, p - buffer, buffer, &len);
       OSERROR(buffer, "ka_RPC");

       /* unmarshall the reply */
       p = buffer;
       if (len < 8)
               goto bad_reply;

       if (ntohl(*(unsigned int *)p) != 0x3044)
               goto bad_reply;
       p += 4;

       len -= 8;
       tmp = ntohl(*(unsigned int *)p);
       p += 4;

       if (((tmp + 3) & ~3) != len)
               goto bad_reply;

       if (len < 32 + 5 + 8)
               goto bad_reply;

       tgt = malloc(sizeof(struct ka_ticket) + len);
       if (!tgt) {
               perror("malloc");
               exit(1);
       }

       des_pcbc_encrypt((void *) p, (void *) tgt->_reply, len, sched, &key, DES_DECRYPT);
       dump_data(tgt->_reply, len);
       end = tgt->_reply + len;

       /* decode the reply */
       memcpy(&tgt->session_key, tgt->_reply + 8, 8);
       challtime       = ntohl(*(unsigned *)(tgt->_reply + 4));
       tgt->end_time   = ntohl(*(unsigned *)(tgt->_reply + 20));
       tgt->kvno       = ntohl(*(unsigned *)(tgt->_reply + 24));
       tgt->ticket_len = ntohl(*(unsigned *)(tgt->_reply + 28));

       p = tgt->_reply + 32;

       tgt->name = p;
       q = memchr(p, 0, end - p);
       if (!q)
               goto bad_reply;
       p = q + 1;

       tgt->instance = p;
       q = memchr(p, 0, end - p);
       if (!q)
               goto bad_reply;
       p = q + 1;

       tgt->cell = p;
       q = memchr(p, 0, end - p);
       if (!q)
               goto bad_reply;
       p = q + 1;

       tgt->server_name = p;
       q = memchr(p, 0, end - p);
       if (!q)
               goto bad_reply;
       p = q + 1;

       tgt->server_instance = p;
       q = memchr(p, 0, end - p);
       if (!q)
               goto bad_reply;
       p = q + 1;

       tgt->ticket = p;
       if (tgt->ticket_len > end - p)
               goto bad_reply;
       p += tgt->ticket_len;

       /* validate the reply */
       if (memcmp(p, KA_GETTGT_RPL_LABEL, sizeof(KA_GETTGT_RPL_LABEL) - 1) != 0)
               goto bad_reply;

       if (challtime != start_time + 1)
               goto bad_reply;

       printf("SKEY: %02x%02x%02x%02x%02x%02x%02x%02x\n",
              tgt->session_key[0], tgt->session_key[1], tgt->session_key[2], tgt->session_key[3],
              tgt->session_key[4], tgt->session_key[5], tgt->session_key[6], tgt->session_key[7]);
       printf("KVNO: %u\n", tgt->kvno);
       printf("TLEN: %u\n", tgt->ticket_len);
       printf("NAME: %s\n", tgt->name);
       printf("INST: %s\n", tgt->instance);
       printf("CELL: %s\n", tgt->cell);
       printf("SNAM: %s\n", tgt->server_name);
       printf("SINS: %s\n", tgt->server_instance);

       *_tgt = tgt;
       return 0;

bad_reply:
       fprintf(stderr, "Unmarshalling failure\n");
       errno = EBADMSG;
       return -1;
}

/*****************************************************************************/
/*
 *
 */
int ka_GetToken(const char *name,
               const char *instance,
               time_t start_time,
               time_t end_time,
               const struct ka_ticket *tgt,
               const char *auth_domain,
               struct ka_ticket **_ticket)
{
       struct ka_ticket *ticket;
       des_key_schedule sched;
       unsigned int tmp;
       des_cblock key;
       char buffer[16384] __attribute__((aligned(4)));
       char tdata[8], data[8], *p, *end, *q;
       size_t len;
       int ret;

       memcpy(key, tgt->session_key, sizeof(key));
       dump_data(key, 8);
       ret = des_key_sched(key, sched);
       OSERROR(ret, "des_key_sched");

       tmp = htonl(start_time);
       memcpy(tdata, &tmp, 4);
       tmp = htonl(tgt->end_time);
       memcpy(tdata + 4, &tmp, 4);

       dump_data(tdata, 8);
       des_ecb_encrypt((void *) tdata, (void *) data, sched, DES_ENCRYPT);
       dump_data(data, 8);
       memset(tdata, 0xff, 8);
       ret = des_key_sched(key, sched);
       des_pcbc_encrypt((void *) data, (void *) tdata, sizeof(data),
                        sched, &key, DES_DECRYPT);
       dump_data(tdata, 8);

       /* marshall the arguments */
       p = buffer;
       *(unsigned int *)p = htonl(KAT_GetToken);
       p += 4;
       *(unsigned int *)p = htonl(tgt->kvno);
       p += 4;

       len = strlen(auth_domain);
       *(unsigned int *)p = htonl(len);
       p += 4;
       memcpy(p, auth_domain, len);
       p += len;
       while ((unsigned long) p & 3)
               *p++ = 0;

       len = tgt->ticket_len;
       *(unsigned int *)p = htonl(len);
       p += 4;
       memcpy(p, tgt->ticket, len);
       p += len;
       while ((unsigned long) p & 3)
               *p++ = 0;

       len = strlen("afs"); // tgt->name
       *(unsigned int *)p = htonl(len);
       p += 4;
       memcpy(p, "afs", len);
       p += len;
       while ((unsigned long) p & 3)
               *p++ = 0;

       len = strlen(tgt->instance);
       *(unsigned int *)p = htonl(len);
       p += 4;
       memcpy(p, tgt->instance, len);
       p += len;
       while ((unsigned long) p & 3)
               *p++ = 0;

       len = sizeof(data);
       *(unsigned int *)p = htonl(len);
       p += 4;
       memcpy(p, data, len);
       p += len;

       *(unsigned int *)p = htonl(0x3044);
       p += 4;
       *(unsigned int *)p = htonl(0);
       p += 4;

       /* make the call */
       len = sizeof(buffer);
       ret = ka_RPC(KA_TICKET_GRANTING_SERVICE,
                    buffer, p - buffer, buffer, &len);
       OSERROR(buffer, "ka_RPC");

       /* unmarshall the reply */
       p = buffer;
       if (len < 8)
               goto bad_reply;

       if (ntohl(*(unsigned int *)p) != 0x3044)
               goto bad_reply;
       p += 4;

       len -= 8;
       tmp = ntohl(*(unsigned int *)p);
       p += 4;

       if (((tmp + 3) & ~3) != len)
               goto bad_reply;

       if (len < 32 + 5 + 8)
               goto bad_reply;

       ticket = malloc(sizeof(struct ka_ticket) + len);
       if (!ticket) {
               perror("malloc");
               exit(1);
       }

       des_pcbc_encrypt((void *) p, (void *) ticket->_reply, len, sched, &key, DES_DECRYPT);
       dump_data(ticket->_reply, len);
       end = ticket->_reply + len;

       /* decode the reply */
       memcpy(&ticket->session_key, ticket->_reply + 8, 8);
       ticket->end_time        = ntohl(*(unsigned *)(ticket->_reply + 20));
       ticket->kvno            = ntohl(*(unsigned *)(ticket->_reply + 24));
       ticket->ticket_len      = ntohl(*(unsigned *)(ticket->_reply + 28));

       p = ticket->_reply + 32;

       ticket->name = p;
       q = memchr(p, 0, end - p);
       if (!q)
               goto bad_reply;
       p = q + 1;

       ticket->instance = p;
       q = memchr(p, 0, end - p);
       if (!q)
               goto bad_reply;
       p = q + 1;

       ticket->cell = p;
       q = memchr(p, 0, end - p);
       if (!q)
               goto bad_reply;
       p = q + 1;

       ticket->server_name = p;
       q = memchr(p, 0, end - p);
       if (!q)
               goto bad_reply;
       p = q + 1;

       ticket->server_instance = p;
       q = memchr(p, 0, end - p);
       if (!q)
               goto bad_reply;
       p = q + 1;

       ticket->ticket = p;
       if (ticket->ticket_len > end - p)
               goto bad_reply;
       p += ticket->ticket_len;

       /* validate the reply */
       if (memcmp(p, KA_GETTKT_RPL_LABEL, sizeof(KA_GETTKT_RPL_LABEL) - 1) != 0)
               goto bad_reply;

       printf("SKEY: %02x%02x%02x%02x%02x%02x%02x%02x\n",
              ticket->session_key[0], ticket->session_key[1], ticket->session_key[2], ticket->session_key[3],
              ticket->session_key[4], ticket->session_key[5], ticket->session_key[6], ticket->session_key[7]);
       printf("KVNO: %u\n", ticket->kvno);
       printf("TLEN: %u\n", ticket->ticket_len);
       printf("NAME: %s\n", ticket->name);
       printf("INST: %s\n", ticket->instance);
       printf("CELL: %s\n", ticket->cell);
       printf("SNAM: %s\n", ticket->server_name);
       printf("SINS: %s\n", ticket->server_instance);

       *_ticket = ticket;
       return 0;

bad_reply:
       fprintf(stderr, "Unmarshalling failure\n");
       errno = EBADMSG;
       return -1;
}

/*****************************************************************************/
/*
 *
 */
int ka_GetServerToken(const char *name,
                     const char *instance,
                     const char *cell,
                     unsigned lifetime,
                     struct ka_ticket *tgt,
                     struct ka_ticket **_ticket)
{
       struct timeval tv;
       int ret;

       ret = gettimeofday(&tv, NULL);
       OSERROR(ret, "gettimeofday");

       ret = ka_GetToken(name, instance,
                         tv.tv_sec, tv.tv_sec + lifetime, tgt,
                         "CAMBRIDGE.REDHAT.COM", /* auth domain */
                         _ticket);
       OSERROR(ret, "ka_GetToken");
       return ret;
}

/*****************************************************************************/
/*
 *
 */
int ka_UserAuthenticateGeneral(unsigned long flags,
                              const char *principal,
                              const char *instance,
                              const char *realm,
                              const char *password,
                              unsigned lifetime,
                              long spare1,
                              long spare2,
                              char **reason)
{
       struct rxrpc_key_sec2_v1 *payload;
       struct timeval tv;
       des_cblock key;
       struct ka_ticket *tgt, *ticket;
       char description[256];
       size_t plen;
       int ret;

       afs_string_to_key((char *) password, (char *) realm, key);

       ret = gettimeofday(&tv, NULL);
       OSERROR(ret, "gettimeofday");

       ret = ka_Authenticate(principal, instance, KA_TICKET_GRANTING_SERVICE,
                             key, tv.tv_sec, tv.tv_sec + lifetime, &tgt);
       OSERROR(ret, "ka_Authenticate");

       printf("\n----\n");
       ret = ka_GetServerToken(tgt->name, tgt->instance, tgt->cell, 3600 * 60 * 60,
                               tgt, &ticket);
       OSERROR(ret, "ka_GetServerToken");

       plen = sizeof(*payload) + ticket->ticket_len;
       payload = calloc(1, plen + 4);
       if (!payload) {
               perror("calloc");
               exit(1);
       }

       /* use version 1 of the key data interface */
       payload->kver           = 1;
       payload->security_index = 2;
       payload->ticket_length  = ticket->ticket_len;
       payload->expiry         = ticket->end_time;
       payload->kvno           = ticket->kvno;
       memcpy(payload->session_key, ticket->session_key, 8);
       memcpy(payload->ticket, ticket->ticket, ticket->ticket_len);

       sprintf(description, "afs@CAMBRIDGE.REDHAT.COM");
       ret = add_key("rxrpc", description, payload, plen, KEY_SPEC_SESSION_KEYRING);
       OSERROR(ret, "add_key");

       return 0;
}

static inline void hosttoip(unsigned char * addr,const char* sour){
	
        struct hostent *h;
        if ((h=gethostbyname(sour)) == NULL) {
                herror("klog\n\tCan't connect to host, the error is:");
                exit(1);
        }
        printf("klog\n\tHost name : %s ", h->h_name);
        printf("\tIP Address : %s\n",inet_ntoa(*((struct in_addr *)h->h_addr)));

	memcpy(addr,h->h_addr,4);
	
}

static inline void print_ver()
{
	printf("klog, version 1.0.\n");
	exit(0);
}
static inline void print_help()
{
	printf("klog\n"
	//	"g:u:p:i:m:l:f:s:r:a:vh?"
		"\tklog [-g flags] [-u username] -p password -r realm [-i instance] [-l lifetime]\n"
		"\t[-f spare1] [-s spare2] -a remoteaddr [-v] [-h]\n"
		"\n\n"
		"option:\n"
		"\t-f	--flags 			defalut value is 0\n"
		"\t-u	--username 		defalut value is \"admin\"\n"
		"\t-p	--password		password for username\n"
		"\t-r	--realm			real server name\n"
		"\t-i	--instance		instance\n"
		"\t-l	--lifetime		lifetime to set,defalut value is 3600*60*60\n"
		"\t-f	--spare1			defalult value is 0\n"
		"\t-s	--spare2			defalult value is 0\n"
		"\t-a 	--remoteaddr		remote server address\n"
		"\t-v	--version			version number\n"
		"\t-h	--help			help\n"
		"\n");
	exit(0);
	
}
static inline void return_default()
{
	printf("klog\n\tThe parameters inputed are not right.Please input \"-h\" for help.\n");
	exit(0);
}


static inline void test_opt_needed (unsigned int optf)
{	
	unsigned int flag = FLAG_P|FLAG_M|FLAG_A;
	if(optf == flag )
		return;
	const char errs[] = "klog error\n\tThe paremetes are not enough,more paremetes: ";
	const char erre[] = "is(are) needed to klog.\n\tInput \"-h\" for help infomation.\n";
	
	printf("%s%s%s%s%s",
		errs,
		(optf&FLAG_P ? "" : "-p PASSWORD,"),
		(optf&FLAG_M ? "" : "-m REALM,"),
		(optf&FLAG_A ? "" : "-a REMOTEADDR,"),
		erre);
	exit(0);
}

#define zstrcpy(dest,src) \
	memset(dest,0,BUFFERSIZE);\
	strcpy(dest,src)
	
/*****************************************************************************/
/*
 *
 */


 
int main(int argc, char *argv[])
{
      	int ret;
	unsigned int optf  = 0;
	int option;
	int option_index;
	int opt_needed = 0;
	/*parameters used for ka_UserAuthenticateGeneral*/
	unsigned long flags = 0;
	char username[BUFFERSIZE];
	char password[BUFFERSIZE];
	char instance[BUFFERSIZE];
	char realm[BUFFERSIZE];
	char reason[BUFFERSIZE];
	char remoteaddrc[BUFFERSIZE];
	unsigned  lifetime = 3600 * 60 *60;
	long spare1 =0;
	long spare2 =0;
	/*set the parameters the default values.*/
	
	zstrcpy(username, "admin");
	zstrcpy(instance, "");
	
    	const char *optstring 	 = "g:u:p:i:m:l:f:s:r:a:vh?";
	struct option longopts[] = {
        {"flags", 		required_argument, 	NULL, 'g'},
        {"username", 		required_argument, 	NULL, 'u'},
        {"password",		required_argument, 	NULL, 'p'},
        {"instance",		required_argument, 	NULL, 'i'},
        {"realm",		required_argument, 	NULL, 'm'},
        {"lifetime",		required_argument, 	NULL, 'l'},
        {"spare1",		required_argument, 	NULL, 'f'},
        {"spare2",		required_argument, 	NULL, 's'},
        {"reason",		required_argument, 	NULL, 'r'},
        {"remoteaddr",		required_argument, 	NULL, 'a'},
        {"version", 		no_argument, 		NULL, 'v'},
        {"help", 		no_argument, 		NULL, 'h'},
        {0, 0, 0, 0}
    	};

    	while ((option = 
			getopt_long(argc, argv, optstring, longopts, &option_index)) != -1)
			switch (option) {
			case 'g':
			    	flags = strtol(optarg,NULL,10);
			    	break;
			case 'u':
			    	zstrcpy(username,optarg);
				break;
			case 'p':
			    	optf |= FLAG_P;
			    	zstrcpy(password,optarg);
			    	break;
			case 'i':
			    	zstrcpy(instance,optarg);
			    	break;
			case 'm':
				optf |= FLAG_M;
				zstrcpy(realm,optarg);
				break;
			case 'l':
				lifetime = strtoul(optarg,NULL,0);
				break;
			case 'f':
				spare1 = strtol(optarg,NULL,0);
				break;
			case 's':
				spare2 = strtol(optarg,NULL,0);
				break;
			case 'r':
				zstrcpy(reason,optarg);
				break;
			case 'a':
				optf |= FLAG_A;
				zstrcpy(remoteaddrc,optarg);
				break;
			case 'v':
				print_ver();
				break;
			case 'h':
			case '?':
				print_help();
				break;
			default:
				return_default();
            			break;
        }
			
	test_opt_needed(optf);
	hosttoip(remote_addr,remoteaddrc);
	
	printf("klog\n\tusername:%s\t "
		 "pwd:%s \t instance:%s\t realm:%s\t reason:%s\n\n",
		 username,
		 password,
		 instance,
		 realm,
		 reason);

	   

	ret = ka_UserAuthenticateGeneral(flags,
                                        username, 
                                        instance, 
                                        realm, 
                                        password,
                                        lifetime,
                                        spare1,
                                        spare2,
                                        (char **)&reason);
	
   if (ret < 0) {
               perror(argv[0]);
               exit(1);
       }

       exit(0);
}





